'''
* This file is part of the Chinchon (https://notabug.org/alkeon/chinchon.
* Copyright (c) 2020 Alejandro "alkeon" Castilla.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
from card import Card
from copy import deepcopy

def to_number(card):
    return card.get_number()

class Hand:
    def __init__(self, provided_list = None): #Default values are static
        if provided_list is None: provided_list = list()
        self.cards = provided_list
    
    def size(self):
        return len(self.cards)

    def set_cards(self, provided_list):
        self.cards = provided_list

    def sort(self):
        self.cards.sort(key = to_number)

    '''
    Calculate points of hand.
    Because of the size of the hand (7), as a maximum there is some limits:
    - Two three of a kind (or one three of a kind and four of a kind)
    - Two straights
    - One straight and two three (or four) of a kind

    First it checks all options for every possibility.

    I couldn't make it better so I decided to check it with three greedy algorithm.
    One that checks three (or four) of a kind
    Other that checks straights
    Other that checks both
    Choice with more points is saved.

    Gets all connected cards with every option and later gets not connected cards
    for best choice, and these points are returned.

    '''
    def calculate_points(self):
        min_points = 70
        position = 0

        straight = list()
        three_of_a_kind = list()

        is_chinchon = False
        three_of_a_kind_options = list()
        straight_options = list()

        sol = list()

        # Get all options from combinations

        while(position < len(self.cards)):

            three_of_a_kind_cards = self.detect_three_and_four_of_a_kind()

            if(len(three_of_a_kind_cards) > 2 and three_of_a_kind_cards not in three_of_a_kind_options):
                three_of_a_kind_options.append(three_of_a_kind_cards)

                #Insert all possible three of a kind from a four of a kind 
                #This allows three of a kind when four aren't possible

                if(len(three_of_a_kind_cards) == 4):

                    i = 0
                    while(i < 4):
                        four_to_three_of_a_kind = deepcopy(three_of_a_kind_cards)
                        four_to_three_of_a_kind.pop(i)
                        if(four_to_three_of_a_kind not in three_of_a_kind_options):
                            three_of_a_kind_options.append(four_to_three_of_a_kind)
                        i += 1

            not_straight_cards, chinchon = self.get_straight(position)

            if(len(not_straight_cards) > 2 and not_straight_cards not in straight_options):
                straight_options.append(not_straight_cards)

            position += 1
            if(chinchon):
                is_chinchon = True

        # Check three of a kind and four of a kind

        i = 0
        points = 0
        while(i < len(three_of_a_kind_options)):
            possible_solution = list()
            temp = 0
            if(i < len(three_of_a_kind_options) and self.can_be_added(possible_solution, three_of_a_kind_options[i])):
                possible_solution += three_of_a_kind_options[i]
            if(i + 1 < len(three_of_a_kind_options)):
                if(self.can_be_added(possible_solution, three_of_a_kind_options[i + 1])):
                    possible_solution += three_of_a_kind_options[i + 1]
            if(i + 2 < len(three_of_a_kind_options)):
                if(self.can_be_added(possible_solution, three_of_a_kind_options[i + 2])):
                    possible_solution += three_of_a_kind_options[i + 2]

            temp = self.get_points(possible_solution)
            if(temp > points):
                sol = deepcopy(possible_solution)
                points = temp
            i += 1

        # Check straight options

        i = 0
        while(i < len(straight_options)):
 
            possible_solution = list()
            if(i < len(straight_options) and self.can_be_added(possible_solution, straight_options[i])):
                possible_solution += straight_options[i]
            if(i + 1 < len(straight_options)):
                if(self.can_be_added(possible_solution, straight_options[i + 1])):
                    possible_solution += straight_options[i + 1]
            if(i + 2 < len(straight_options)):
                if(self.can_be_added(possible_solution, straight_options[i + 2])):
                    possible_solution += straight_options[i + 2]
            temp = self.get_points(possible_solution)
            if(temp > points):
                sol = deepcopy(possible_solution)
                points = temp
            i += 1

        # Check straight options and three and four of a kind

        i = 0
        while(i < len(straight_options)):

            e = 0
            while(e < len(three_of_a_kind_options)):
                
                possible_solution = list()
                possible_solution += straight_options[i]
                if(e < len(three_of_a_kind_options) and self.can_be_added(possible_solution, three_of_a_kind_options[e])):
                    possible_solution += three_of_a_kind_options[e]
                if(e + 1 < len(three_of_a_kind_options)):
                    if(self.can_be_added(possible_solution, three_of_a_kind_options[e + 1])):
                        possible_solution += three_of_a_kind_options[e + 1]
                if(e + 2 < len(three_of_a_kind_options)):
                    if(self.can_be_added(possible_solution, three_of_a_kind_options[e + 2])):
                        possible_solution += three_of_a_kind_options[e + 2]

                temp = self.get_points(possible_solution)

                if(temp > points):
                    sol = deepcopy(possible_solution)
                    points = temp
                e += 1
            i += 1

        not_connected_cards = self.get_not_connected_cards(sol)

        min_points = self.get_points(not_connected_cards)
        if(min_points == 0 and is_chinchon == True):
            min_points = -100
        elif(min_points == 0):
            min_points = -10
        return min_points
    '''
    From connected cards, checks hand for not connected cards
    '''
    def get_not_connected_cards(self, connected):
        i = 0
        not_connected_cards = list()
        while(i < len(self.cards)):
            e = 0
            found = False
            while(e < len(connected)):
                if(self.cards[i].get_number() == connected[e].get_number()
                   and self.cards[i].get_suit() == connected[e].get_suit()):
                    found = True
                e += 1
            if(not found):
                not_connected_cards.append(self.cards[i])
            i += 1
        return not_connected_cards
    '''
    Checks if option can be added to solution
    '''
    def can_be_added(self, sol, cards_to_be_append):
        i = 0
        added = True
        while(i < len(self.cards)):
            if(self.cards[i] in sol and self.cards[i] in cards_to_be_append):
                added = False
            i += 1
        return added
    '''
    Takes card from position and check if the rest of the cards can be used for a straight
    also sends chinchon true if hand is a full straight
    '''
    def get_straight(self, position):
        possible_straight = list()
        first_card = self.cards[position]

        possible_straight = self.get_cards_same_suit(first_card.get_suit())
        chinchon = (len(possible_straight) == 7)
        possible_straight.sort(key = to_number)
        
        straight_cards = self.check_straight(possible_straight)
        chinchon = chinchon and (len(straight_cards) == 7)
        return straight_cards, chinchon


    '''
    Verify if cards is a valid straight
    '''
    def check_straight(self, possible_straight):

        position = 0
        return_straighted_cards = list()
        continuous_straight = 0
        not_straight_cards = list()
        straighted_cards = list()
        while(position < len(possible_straight)):
            if(position + 1 < len(possible_straight)):

                if(to_number(possible_straight[position]) !=
                   to_number(possible_straight[position + 1]) - 1):
                    if(continuous_straight < 2):
                        continuous_straight = 0
                    if(to_number(possible_straight[position - 1]) + 1 !=
                       to_number(possible_straight[position])):
                        not_straight_cards.insert(0, possible_straight[position])
                    else:
                        straighted_cards.insert(0, possible_straight[position])
                else:
                    straighted_cards.insert(0, possible_straight[position])
                    continuous_straight += 1
            else:
                if(to_number(possible_straight[position - 1]) + 1 !=
                   to_number(possible_straight[position])):
                    not_straight_cards.insert(0, possible_straight[position])
                    
                else:
                    straighted_cards.insert(0, possible_straight[position])

            position += 1

        if(continuous_straight < 2):
            return_straighted_cards = list()
        else:
            position = 0
            while(position < len(self.cards)):
                if(self.cards[position] in straighted_cards and
		    self.cards[position] not in return_straighted_cards):
                    return_straighted_cards.insert(0, self.cards[position])
                position += 1
        

        return return_straighted_cards
    '''
    Checks every card from hand for a three (or four) of a kind
    '''
    def detect_three_and_four_of_a_kind(self):
        position = 0
        return_three_of_a_kind_cards = list()
        three_of_a_kind_cards = list()
        while(position < len(self.cards)):
            provided_card = self.cards[position]
            new_position = 0
            possible_three_of_a_kind_cards = list()

            while(new_position < len(self.cards)):
                card = self.cards[new_position]
                if(to_number(provided_card) == to_number(card)):
                    possible_three_of_a_kind_cards.insert(0, card)
            
                new_position += 1
            if(len(possible_three_of_a_kind_cards) > 2):
                three_of_a_kind_cards += possible_three_of_a_kind_cards
            position += 1

        position = 0
		# Delete repeated cards
        while(position < len(self.cards)):
            if(self.cards[position] in three_of_a_kind_cards):
                return_three_of_a_kind_cards.insert(0, self.cards[position])
            position += 1
                
        return return_three_of_a_kind_cards
    
    def get_cards_same_suit(self, suit):
        position = 0
        same_suit_cards = list()
        while(position < len(self.cards)):
            card = self.cards[position]
            if(suit == card.get_suit()):
                same_suit_cards.insert(0, card)
            position += 1
        return same_suit_cards

    def insert(self, position, object):
        self.cards.insert(position, object)

    def pop(self, position):
        self.cards.pop(position)

    def get_cards(self):
        return self.cards.copy()

    '''
    Sum of all points from cards
    Every card adds its own number unless 11 and 12, those add 10 points.
    '''
    def get_points(self, cards):
        position = 0
        points = 0
        set_seen = set()
        while(position < len(cards)):
            if(cards[position] not in set_seen):
                first_number = to_number(cards[position])
                if(first_number < 11):
                    points += first_number
                else:
                    points += 10
                set_seen.add(cards[position])
            position += 1

        return points

    def get(self, position):
        return self.cards[position]

    def __str__(self):
        i = 0
        string_result = str()
        while(i < self.size()):
            string_result += str(i) + ".- " + str(self.cards[i]) + "   "
            i += 1
        return string_result
